/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: dts reg operation language support
 * Author: xiaowei
 * Create: 2019-07-18
 */
#include <linux/of.h>
#include <linux/hal/drol.h>
#include <asm/io.h>

static int drol_getprop_reg(int *addr_cells, phys_addr_t *reg, struct device_node *np)
{
	unsigned int reg32;
	unsigned long long reg64;
	int ret;

	if (*addr_cells == 0) {
		*addr_cells = of_n_addr_cells(np);
		if (!addr_cells_range_ok(*addr_cells))
			return -EINVAL;
	}

	if (*addr_cells == 1) {
		ret = of_property_read_u32(np, "reg", &reg32);
		*reg = (phys_addr_t)reg32;
	} else {
		ret = of_property_read_u64(np, "reg", &reg64);
		*reg = (phys_addr_t)reg64;
	}

	return ret;
}

static void drol_getprop_op(unsigned int *op, struct device_node *np)
{
	*op = DROL_OP_WRITE | DROL_OP_READ;

	if (of_property_match_string(np, "op", "readonly") >= 0)
		*op = DROL_OP_READ;
	else if (of_property_match_string(np, "op", "writeonly") >= 0)
		*op = DROL_OP_WRITE;
}

static void drol_getprop_u32(unsigned int *prop, struct device_node *np, const char *name)
{
	if (of_property_read_u32(np, name, prop))
		*prop = 0;
}

int drol_get_op(struct drol_op *op, int op_size, const char *name, const void *dtb_addr)
{
	int i = 0;
	int ret;
	int addr_cells = 0;
	struct device_node *np = NULL;
	struct device_node *node = NULL;

	if (!op || !name)
		return -EINVAL;

	node = of_find_compatible_node(NULL, NULL, name);
	if (!node)
		return -ENODEV;

	for_each_child_of_node(node, np) {
		if (i >= op_size)
			return -ENOMEM;

		ret = drol_getprop_reg(&addr_cells, &op[i].reg, np);
		if (ret)
			return ret;

		drol_getprop_op(&op[i].op, np);

		drol_getprop_u32(&op[i].clear, np, "clear");
		drol_getprop_u32(&op[i].set, np, "set");
		drol_getprop_u32(&op[i].xor, np, "xor");
		drol_getprop_u32(&op[i].delay, np, "delay");

		i++;
	}
	return i;
}

void __init *drol_early_ioremap(phys_addr_t phys)
{
	return do_drol_early_ioremap(phys);
}

void __init drol_early_iounmap(void *virt)
{
	do_drol_early_iounmap(virt);
}

void *drol_ioremap(phys_addr_t phys)
{
	return ioremap(phys, REG_SIZE);
}

void drol_iounmap(void *virt)
{
	iounmap(virt);
}

/*
 * drol_do_op_once - do drol op only one time
 * @name: the strings of the device's "compatible" property
 * @op: the struct drol_op array to store registers operation info
 * @op_size: op array size
 * return: number of op
 */
int drol_do_op_once(const char *name, struct drol_op *op, int op_size)
{
	int nr;
	int ret;

	nr = drol_get_op(op, op_size, name, NULL);
	if (nr <= 0)
		return 0;

	ret = drol_map_op(op, nr, DROL_IOREMAP, DROL_IOUNMAP);
	if (ret)
		return ret;

	drol_do_op(op, nr);
	drol_unmap_op(op, nr, DROL_IOUNMAP);
	return nr;
}
EXPORT_SYMBOL_GPL(drol_do_op_once);
